/**	
*	
	Doyle Spiral : 
	introducing scaled rectangle
	aimed to draw elementary circles to understand the theory and the computations to be done
*/
// Interesting values : a=1.2766205 =6 m=27
import ij.ImagePlus;
import ij.plugin.PlugIn;
import ij.process.ColorProcessor;
import ij.process.ImageProcessor;


import java.awt.Color;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Polygon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.PrintStream;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Random; 
import java.util.TreeSet;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


import pluginUtils.Circle;
import pluginUtils.CircleInversion;
import pluginUtils.Complexe;
import pluginUtils.DoyleCircle;


public class DoyleInv extends ImagePlus implements PlugIn,ChangeListener,ActionListener{
	{Locale.setDefault(Locale.US); }
	private CircleInversion cinv=new CircleInversion(new Circle(Complexe.createComplexeFromRI(0,0),1));
	private ImageProcessor primeIP; 
	private int X=2000,Y=2000; // image size
	
	private Random generator=new Random(); // One always needs some randomness
	
	// For the scaling of the image
	private double x0=0,x1=1,y0=0,y1=1;
	private double acoef,ccoef,vcoef,wcoef; 
	
	
	
	public void setCoefs(){
		acoef=X/(x1-x0); 
		vcoef=Y/(y1-y0); 
		ccoef=-acoef*x0; 
		wcoef=-vcoef*y0; 
	}
	 
	private PrintStream output;
	private double  n=34.0; //13.0; 
	private double  m=30.0; //11.0; 
	
	private int module=(int)(n); 
	
	private double r=0.01;
	private double rmin=0.01; 
	private double rmax=2.7; 
	
	private double a=1.17115; 
	private double amin=a; 
	private double amax=a+0.04;
	
	private double b=Math.pow(Math.pow(a,n),1.0/m);   
	private double bmin=b;
	private double bmax=b+0.05;
	
	private boolean light=true; 
	
	private int maxIter=300; 
	private int maxIterMin=300; 
	private int maxIterMax=4000; 
	
	
	private JSlider slidea= new JSlider(JSlider.HORIZONTAL,0, 100, 0);
	private JSlider slideb= new JSlider(JSlider.HORIZONTAL,0, 100, 0);
	private JSlider slider= new JSlider(JSlider.HORIZONTAL,0, 100, 0);
	private JSlider slidem= new JSlider(JSlider.HORIZONTAL,0, 100, 0);
	
	private JLabel aValue=new JLabel("A : "+a); 
	private JLabel bValue=new JLabel("B : "+b);
	private JLabel rValue=new JLabel("R : "+r);
	private JLabel mValue=new JLabel("Iter : "+maxIter); 
	
	
	private JRadioButton minimal=new JRadioButton("Minimal"); 
	private JRadioButton complete=new JRadioButton("Complete"); 
	
	private JRadioButton creuxb=new JRadioButton("Creux"); 
	private JRadioButton plein=new JRadioButton("Plein"); 
	
	
	private ButtonGroup group = new ButtonGroup();
	{
	minimal.setActionCommand("Minimal");
	complete.setActionCommand("Complete");  
    group.add(minimal);
    group.add(complete);
    minimal.addActionListener(this); 
    complete.addActionListener(this); 
    minimal.setSelected(true); 
	}
	
	private ButtonGroup groupc = new ButtonGroup();
	{
	creuxb.setActionCommand("Creux"); 
	creuxb.setSelected(true); 
	plein.setActionCommand("Plein");  
    groupc.add(creuxb);
    groupc.add(plein);
    creuxb.addActionListener(this); 
    plein.addActionListener(this);  
	}
	
	
    
    


	
	
	private ArrayList<DoyleCircle> memoire=new ArrayList<DoyleCircle>();
	private ArrayList<DoyleCircle> traites=new ArrayList<DoyleCircle>();
	private ArrayList<DoyleCircle> traces=new ArrayList<DoyleCircle>();
	
	public void setBounds(double xl,double xu,double yl,double yu){
		x0=xl; 
		x1=xu;
		y0=yl;
		y1=yu; 
	}
	
	
	
	private float redColor[]=new float[module]; 
	private float blueColor[]=new float[module];
	private float greenColor[]=new float[module];
	
	private boolean creux=true; 
	
	private void drawCircle(DoyleCircle c){
		//if(intersect(c))return; 
		
		//if(c.getRadius()<0.1) return; 
		int intX=(int)(c.getXcenter()*acoef+ccoef);
		int intY=(int)(c.getYcenter()*vcoef+wcoef);
		int intRx=(int)(c.getRadius()*acoef);
		int intRy=(int)(c.getRadius()*vcoef);
		
		int p=c.getApower(); 
		int q=c.getBpower(); 
		while(p<0){p+=n; q+=m; }
		while(p>n){p-=n; q-=m;}
			int rank= p%module; 
			
		
		if(creux)	
		primeIP.drawOval(intX-intRx,intY-intRy,intRx*2,intRy*2);
		else{
			
			primeIP.setColor(new Color(redColor[rank],greenColor[rank],blueColor[rank]));
			primeIP.fillOval(intX-intRx,intY-intRy,intRx*2,intRy*2);
			
			double Xs=c.getXcenter(); 
			double Ys=c.getYcenter(); 
			double rapp=Xs*Xs+Ys*Ys+1; 
			double u=Xs/rapp; 
			double v=Ys/rapp; 
			double w=(Xs*Xs+Ys*Ys)/rapp;
			
			double Xsr=c.getXcenter(); 
			double Ysr=c.getYcenter()+c.getRadius(); 
			double rappr=Xsr*Xsr+Ysr*Ysr+1; 
			double ur=Xsr/rappr; 
			double vr=Ysr/rappr; 
			double wr=(Xsr*Xsr+Ysr*Ysr)/rappr;
			
			double nrad=(u-ur)*(u-ur)+(v-vr)*(v-vr)+(w-wr)*(w-wr);
			nrad=Math.sqrt(nrad)*1.414; 
			
			//String s="sphere{<"+u+","+v+","+w+">,";
			//String s="sphere{<"+c.getXcenter()*X+",0,"+c.getYcenter()*Y+">,";
			//s+=c.getRadius()*X;
			//s+=nrad; 
			//s+=" texture{Texture"+rank+"} finish{Finish"+rank+"}}"; 
			//System.out.println(s);
			
			double distance=(c.getXcenter()+0.5)*(c.getXcenter()+0.5); 
			distance+=(c.getYcenter()+0.5)*(c.getYcenter()+0.5); 
			distance=Math.exp(-distance/0.5); 
			String s="sphere{<"+c.getXcenter()+",0,"+c.getYcenter()+">,";
			//s+="<"+c.getXcenter()+","+(0.5+5*distance)+","+c.getYcenter()+">,"+
			s+=c.getRadius();
			s+=" texture{Texture"+rank+"}}";
			System.out.println(s);
			output.println(s);
			
			
		}
		if(creux){
		if((c.getBpower()==0)&&(c.getApower()>0)){
			primeIP.setColor(Color.BLACK); 
			primeIP.fillOval(intX-intRx,intY-intRy,intRx*2,intRy*2);	
			primeIP.setColor(Color.LIGHT_GRAY);
			primeIP.moveTo(intX-85,intY+20); 
			primeIP.drawString(c.getApower()+""); 
			primeIP.setColor(Color.BLACK); 
		}
		if((c.getBpower()>0)&&(c.getApower()==0)){
			primeIP.setColor(Color.RED); 
			primeIP.fillOval(intX-intRx,intY-intRy,intRx*2,intRy*2);
			primeIP.setColor(Color.YELLOW);
			primeIP.moveTo(intX-85,intY+20); 
			primeIP.drawString(c.getBpower()+""); 
			primeIP.setColor(Color.RED); 
		}
		}
		if(c.getName()!=null){
		//primeIP.moveTo(intX-85,intY+20); 
		//primeIP.drawString(c.getName());
		}
	}
	
	
	private void drawCircle2(DoyleCircle c){
		//if(intersect(c))return; 
		if(c.getRadius()<0.01) return;
		// Computing radius from Bpower and Apower
		double radius=r*Math.pow(a,c.getApower())*Math.pow(b,c.getBpower()); 
		
		int intX=(int)(c.getXcenter()*acoef+ccoef);
		int intY=(int)(c.getYcenter()*vcoef+wcoef);
		int intRx=(int)(radius*acoef);
		int intRy=(int)(radius*vcoef);
	
			int rank=Math.abs(c.getApower()%module); 
		
		if(creux)	
		primeIP.drawOval(intX-intRx,intY-intRy,intRx*2,intRy*2);
		else{
			primeIP.setColor(new Color(redColor[rank],greenColor[rank],blueColor[rank]));
			primeIP.fillOval(intX-intRx,intY-intRy,intRx*2,intRy*2);			
		}
		if((c.getBpower()==0)&&(c.getApower()>0)){
			primeIP.setColor(Color.BLACK); 
			primeIP.fillOval(intX-intRx,intY-intRy,intRx*2,intRy*2);	
			primeIP.setColor(Color.LIGHT_GRAY);
			primeIP.moveTo(intX-85,intY+20); 
			primeIP.drawString(c.getApower()+""); 
			primeIP.setColor(Color.BLACK); 
		}
		if((c.getBpower()>0)&&(c.getApower()==0)){
			primeIP.setColor(Color.RED); 
			primeIP.fillOval(intX-intRx,intY-intRy,intRx*2,intRy*2);
			primeIP.setColor(Color.YELLOW);
			primeIP.moveTo(intX-85,intY+20); 
			primeIP.drawString(c.getBpower()+""); 
			primeIP.setColor(Color.RED); 
		}
		if(c.getName()!=null){
		//primeIP.moveTo(intX-85,intY+20); 
		//primeIP.drawString(c.getName());
		}
	}
	
	
	private void drawLine(double xd,double yd,double xf,double yf){
		int XD=(int)(xd*acoef+ccoef); 
		int YD=(int)(yd*vcoef+wcoef); 
		int XF=(int)(xf*acoef+ccoef); 
		int YF=(int)(yf*vcoef+wcoef);
		primeIP.drawLine(XD,YD,XF,YF); 
	}
	
	{
		 JFrame frame = new JFrame("Settings");
	        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	        frame.setPreferredSize(new Dimension(800,200)); 
	        GridLayout layout = new GridLayout(6,3);
	        frame.getContentPane().setLayout(layout); 
	        
	        //	      Setting A
	        
	        frame.getContentPane().add(aValue);
	      
	        
	        Hashtable<Integer,JLabel> labelTableA = new Hashtable<Integer,JLabel>();
	        labelTableA.put( new Integer( 0 ), new JLabel(amin+"") );
	        labelTableA.put( new Integer( 100 ), new JLabel(amax+"") );
	        slidea.setLabelTable( labelTableA );
	        slidea.setPaintLabels(true);
	        frame.getContentPane().add(slidea);
	        slidea.addChangeListener(this);
	        
	        //frame.getContentPane().add(new JLabel("")); 

	        //Setting B
	        
	        frame.getContentPane().add(bValue);
	       
	         
	        Hashtable<Integer,JLabel> labelTable = new Hashtable<Integer,JLabel>();
	        labelTable.put( new Integer( 0 ), new JLabel(bmin+"") );
	        labelTable.put( new Integer( 100 ), new JLabel(bmax+"") );
	        slideb.setLabelTable( labelTable );
	        slideb.setPaintLabels(true);
	        frame.getContentPane().add(slideb);
	        slideb.addChangeListener(this);
	        
	        frame.getContentPane().add(rValue);
	        
	         
	        Hashtable<Integer,JLabel> labelTabler = new Hashtable<Integer,JLabel>();
	        labelTabler.put( new Integer( 0 ), new JLabel(rmin+"") );
	        labelTabler.put( new Integer( 100 ), new JLabel(rmax+"") );
	        slider.setLabelTable( labelTabler );
	        slider.setPaintLabels(true);
	        frame.getContentPane().add(slider);
	        slider.addChangeListener(this);
	        frame.getContentPane().add(mValue);
	        
	        
	        Hashtable<Integer,JLabel> labelTablem = new Hashtable<Integer,JLabel>();
	        labelTablem.put( new Integer( 0 ), new JLabel(maxIterMin+"") );
	        labelTablem.put( new Integer( 100 ), new JLabel(maxIterMax+"") );
	        slidem.setLabelTable( labelTablem );
	        slidem.setPaintLabels(true);
	        
	        frame.getContentPane().add(mValue);
	        frame.getContentPane().add(slidem);
	        slidem.addChangeListener(this);
	        
	       frame.getContentPane().add(minimal);
	       frame.getContentPane().add(complete);
	       frame.getContentPane().add(creuxb);
	       frame.getContentPane().add(plein);
	       
	        //Display the window.
	        frame.pack();
	        frame.setVisible(true);
	        
	       
	}

	
	public void stateChanged(ChangeEvent e) {
	    JSlider source = (JSlider)e.getSource();
	    if (!source.getValueIsAdjusting()) {
	        int fps = (int)source.getValue();
	        if(source==slideb){
	        b=bmin+(bmax-bmin)*(fps+0.0)/100.0;
	        bValue.setText("B :"+b);
	        }
	        if(source==slidea){
	        a=amin+(amax-amin)*(fps+0.0)/100.0;
	        aValue.setText("A :"+a); 
	        }
	        if(source==slider){
		        r=rmin+(rmax-rmin)*(fps+0.0)/100.0;
		        rValue.setText("R :"+r); 
		        }
	        if(source==slidem){
		        maxIter=maxIterMin+(maxIterMax-maxIterMin)*fps/100;
		        mValue.setText("Iter :"+maxIter); 
		        }
	        
	        this.run(null); 
	    }
	}
	
	public void actionPerformed(ActionEvent e) {
		String command=e.getActionCommand(); 
		if(command=="Minimal") light=true; 
		if(command=="Complete") light=false; 
		if(command=="Creux") creux=true; 
		if(command=="Plein") creux=false; 
	  this.run(null); 
	} 

	
	
	public void run(String arg) {
		
		
		primeIP = new ColorProcessor(X,Y);
		
		System.out.println("--->"+Math.pow(Math.pow(a,26.0),1/3.0));
		DoyleCircle.setBounds((int)n, (int)m); 
		
		double limit=25;
		setBounds(-limit,limit,-limit,limit); 
		setCoefs(); 
		
		
		System.out.println(Math.pow(a,n)+" "+Math.pow(b,m)); 
		
		float cseed=0.0f; 
		float step=1.0f/module; 
		for(int i=0;i<module;i++){
			blueColor[i]=cseed; 
			redColor[i]=0.2f+1.6f*cseed*(1-cseed); 
			greenColor[i]=0.2f; 
			cseed+=step; 
		}
		
		
		// Calcul des angles
		double u[]={1+1/a+b/a,1+1/a+1/b,1+1/b+a/b,1+a+a/b,1+a+b,1+b+b/a};
		double theta[]={Math.acos((u[0]-b/(a*a))/(u[0]+b/(a*a))),
						Math.acos((u[1]-1/(a*b))/(u[1]+1/(a*b))),
						Math.acos((u[2]-a/(b*b))/(u[2]+a/(b*b))),
						Math.acos((u[3]-a*a/b)/(u[3]+a*a/b)),
						Math.acos((u[4]-a*b)/(u[4]+a*b)),
						Math.acos((u[5]-b*b/a)/(u[5]+b*b/a))
		};
	
		
		double radiusCoef[]={b/a,1.0/a,1.0/b,a/b,a,b};
		int modifa[]={-1,-1,0,1,1,0};
		int modifb[]={1,0,-1,-1,0,1};
		
		
		double rotationAngle[]=new double[6];
		rotationAngle[0]=Math.PI-(theta[0]+theta[1]+theta[2]); 
		rotationAngle[1]=Math.PI-(theta[1]+theta[2]+theta[3]); 
		rotationAngle[2]=Math.PI-(theta[2]+theta[3]+theta[4]);
		rotationAngle[3]=Math.PI-(theta[3]+theta[4]+theta[5]); 
		rotationAngle[4]=Math.PI-(theta[4]+theta[5]+theta[0]); 
		rotationAngle[5]=Math.PI-(theta[5]+theta[0]+theta[1]); 
		
		double ra1=theta[4]*n*180/Math.PI; 
		while(ra1>360) ra1-=360; 
		double ra2=theta[5]*m*180/Math.PI;
		while(ra2>360) ra2-=360; 
		System.out.println("Compare angle "+ra1+" "+ra2);
		
		Polygon background=new Polygon(new int[]{0,0,X,Y},new int[]{0,X,Y,0},4);
		primeIP.setColor(Color.WHITE);
		
		primeIP.fillPolygon(background);
		
		
		primeIP.setFont(new Font(null,Font.ITALIC,60));
		primeIP.setColor(Color.BLACK);
		primeIP.setLineWidth(5); 
		
		//primeIP.moveTo(150,80); 
		//primeIP.drawString("a="+a+"\n"+"b="+b+"\n"+"n="+n+"\n"+"m="+m);
	
		setProcessor("Verifying", primeIP);
		/************************ The drawing begins here *******************************/
		if(light){
		drawLine(0,y0,0,y1); 
		drawLine(x0,0,x1,0);
		DoyleCircle init=new DoyleCircle(0.5,0.5,Math.PI-theta[5]-theta[4],r,0,0); // 0.07
		
		drawCircle(init); 
		
		
		for(int i=0;i<100;i++){
			double theta0=init.getRotation(); 
			double rad1=init.getRadius()*(1+radiusCoef[4]); 
			double nX1=init.getXcenter()+rad1*Math.cos(theta0);
			double nY1=init.getYcenter()+rad1*Math.sin(theta0);
			double angle1=init.getRotation()+rotationAngle[4]; 
			int ca=init.getApower(); 
			int cb=init.getBpower(); 
			DoyleCircle c1=new DoyleCircle(nX1,nY1,angle1,init.getRadius()*radiusCoef[4],
					ca+modifa[4],cb+modifb[4]); 
			c1.setName(i+"");
			drawCircle(c1); 
			init=c1; 
		}
		
		init=new DoyleCircle(0.5,0.5,Math.PI-theta[5],r,0,0);
		primeIP.setColor(Color.RED);
		for(int i=0;i<100;i++){
			double theta0=init.getRotation(); 
			double rad1=init.getRadius()*(1+radiusCoef[5]); 
			double nX1=init.getXcenter()+rad1*Math.cos(theta0);
			double nY1=init.getYcenter()+rad1*Math.sin(theta0);
			double angle1=init.getRotation()+rotationAngle[5]; 
			int ca=init.getApower(); 
			int cb=init.getBpower(); 
			DoyleCircle c1=new DoyleCircle(nX1,nY1,angle1,init.getRadius()*radiusCoef[5],
					ca+modifa[5],cb+modifb[5]); 
			c1.setName(i+"");
			drawCircle(c1); 
			init=c1; 
		}
		}// not launch
		/*******************************************/
		else
		{
				System.out.println("// *******************************************Begin");
				try{
					output=new PrintStream("outdoyle.txt");
					}
					catch(Exception e){System.out.println("Probleme"); System.exit(0);}
			memoire=new ArrayList<DoyleCircle>();
			traites=new ArrayList<DoyleCircle>();
			traces=new ArrayList<DoyleCircle>();
			if(creux){
		drawLine(0,y0,0,y1); 
		drawLine(x0,0,x1,0);
			}
		DoyleCircle init=new DoyleCircle(0.1,0.1,0,0.01,0,0); // 0.07
		
		drawCircle(cinv.inverte(init)); 
		memoire.add(init);
		traces.add(init); 
		try{
			output=new PrintStream("outdoyle.txt");
			}
			catch(Exception e){System.out.println("Probleme"); System.exit(0);}
		int iter=0; 
		while((!memoire.isEmpty())&&(iter++<maxIter)){
			int index=0; 
			DoyleCircle seed=memoire.get(index); //memoire.get(index); 
			memoire.remove(seed); 
			if(!traites.contains(seed)){
				traites.add(seed); 
				if(!traces.contains(seed)) {drawCircle(cinv.inverte(seed));traces.add(seed);} 
				
				double theta0=seed.getRotation()+Math.PI; 
				
				for(int k=0;k<6;k++){
					double rad1=seed.getRadius()*(1+radiusCoef[k]); 
					double nX1=seed.getXcenter()+rad1*Math.cos(theta0);
					double nY1=seed.getYcenter()+rad1*Math.sin(theta0);
					double angle1=seed.getRotation()+rotationAngle[k]; 
					int ca=seed.getApower(); 
					int cb=seed.getBpower(); 
					DoyleCircle c1=new DoyleCircle(nX1,nY1,angle1,seed.getRadius()*radiusCoef[k],
							ca+modifa[k],cb+modifb[k]); 
					if((c1.getRadius()>0.00005)&&(c1.getRadius()<100))
						if(!memoire.contains(c1)) 
					memoire.add(c1);
					theta0+=theta[k];
					}//
			}// if not traite
		}// while memoire ! empty
		
		for(DoyleCircle e: memoire) drawCircle(cinv.inverte(e));
		}// else (launch)
		/*************************************************/
		
		
		show();
		System.out.println("**********************************************done "+b); 
	}	
}
	
	
	